import {
  useRef,
  isValidElement,
} from 'react'
import {
  useSpring,
  animated,
} from '@react-spring/web'
import classNames from 'classnames'
import { bound } from '@/library/tool-kits/bound'
import { useThrottleFn } from '@/library/hooks/use-throttle-fn'
import { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect'
import { useIsomorphicUpdateLayoutEffect } from '../../hooks/use-isomorphic-update-layout-effect'
import { usePropsValue } from '../../hooks/use-props-value'
import { useResizeEffect } from '../../hooks/use-resize-effect'
import { useMutationEffect } from '../../hooks/use-mutation-effect'
import { traverseReactNode } from '../../utils/traverse-react-node'
import { withNativeProps } from '../../utils/native-props'
import { ShouldRender } from '../../utils/should-render'
import type {
  TabProps,
  TabsProps,
} from './type'

const classPrefix = 'rt-tabs'

export const Tab: React.FC<TabProps> = () => null

export const Tabs: React.FC<TabsProps> = (props) => {
  const {
    autoScroll = true,
    activeKey,
    activeLineMode = 'auto',
    children,
    defaultActiveKey,
    direction = 'ltr',
    onChange,
    stretch = true,
  } = props

  const tabListContainerRef = useRef<HTMLDivElement>(null)
  const activeLineRef = useRef<HTMLDivElement>(null)
  const keyToIndexRecord: Record<string, number> = {}
  let firstActiveKey: string | null = null

  const panes: React.ReactElement<TabProps>[] = []

  // const isRTL = direction === 'rtl'

  traverseReactNode(children, (child, index) => {
    if (!isValidElement<TabProps>(child)) return

    const { key } = child
    if (typeof key !== 'string') return
    if (index === 0) firstActiveKey = key

    const length = panes.push(child)
    keyToIndexRecord[key] = length - 1
  })

  const [actActiveKey, setActActiveKey] = usePropsValue({
    value: activeKey,
    defaultValue: defaultActiveKey ?? firstActiveKey,
    onChange: (v) => {
      if (v === null) return
      onChange?.(v)
    },
  })

  const [{ x, width }, inkApi] = useSpring(() => ({
    x: 0,
    width: 0,
    config: {
      tension: 300,
      clamp: true,
    },
  }))

  const [{ scrollLeft }, scrollApi] = useSpring(() => ({
    scrollLeft: 0,
    config: {
      tension: 300,
      clamp: true,
    },
  }))

  const [{ leftMaskOpacity, rightMaskOpacity }, maskApi] = useSpring(() => ({
    leftMaskOpacity: 0,
    rightMaskOpacity: 0,
    config: {
      clamp: true,
    },
  }))

  function animate(immediate = false, fromMutation = false) {
    const container = tabListContainerRef.current
    if (!container) return

    const activeIndex = keyToIndexRecord[actActiveKey as string]
    if (activeIndex === undefined) {
      inkApi.start({
        x: 0,
        width: 0,
        immediate: true,
      })
      return
    }

    const activeLine = activeLineRef.current
    if (!activeLine) return

    const activeTabWrapper = container.children.item(
      activeIndex + 1,
    ) as HTMLDivElement
    const activeTab = activeTabWrapper.children.item(0) as HTMLDivElement
    const activeTabLeft = activeTab.offsetLeft
    const activeTabWidth = activeTab.offsetWidth
    const activeTabWrapperLeft = activeTabWrapper.offsetLeft
    const activeTabWrapperWidth = activeTabWrapper.offsetWidth

    const containerWidth = container.offsetWidth
    const containerScrollWidth = container.scrollWidth
    const containerScrollLeft = container.scrollLeft

    const activeLineWidth = activeLine.offsetWidth

    let rx = 0
    let rw = 0
    if (activeLineMode === 'auto') {
      rx = activeTabLeft
      rw = activeTabWidth
    } else if (activeLineMode === 'full') {
      rx = activeTabWrapperLeft
      rw = activeTabWrapperWidth
    } else {
      // 居中在 Tab 下方
      rx = activeTabLeft + (activeTabWidth - activeLineWidth) / 2
    }

    inkApi.start({
      x: rx,
      width: rw,
      immediate, // Prevents the animation if true.
    })

    const maxScrollDistance = containerScrollWidth - containerWidth
    if (maxScrollDistance <= 0) return

    let nextScrollLeft = 0

    nextScrollLeft = bound(
      activeTabLeft - (containerWidth - activeTabWidth) / 2,
      0,
      maxScrollDistance,
    )
    if (!fromMutation && autoScroll === true) {
      scrollApi.start({
        scrollLeft: nextScrollLeft,
        from: { scrollLeft: containerScrollLeft },
        immediate,
      })
    }
  }

  // 首次无动画
  useIsomorphicLayoutEffect(() => {
    animate(!x.isAnimating)
  }, [])

  useIsomorphicUpdateLayoutEffect(() => {
    animate()
  }, [actActiveKey])

  useResizeEffect(() => {
    animate(!x.isAnimating)
  }, tabListContainerRef)

  useMutationEffect(
    () => {
      animate(!x.isAnimating, true)
    },
    tabListContainerRef,
    {
      subtree: true,
      childList: true,
      characterData: true,
    },
  )

  const { run: updateMask } = useThrottleFn(
    (immediate = false) => {
      const container = tabListContainerRef.current
      if (!container) return

      const sl = container.scrollLeft

      let showLeftMask = false
      let showRightMask = false

      showLeftMask = sl > 0
      showRightMask = sl + container.offsetWidth < container.scrollWidth

      maskApi.start({
        leftMaskOpacity: showLeftMask ? 1 : 0,
        rightMaskOpacity: showRightMask ? 1 : 0,
        immediate,
      })
    },
    {
      wait: 100,
      trailing: true,
      leading: true,
    },
  )

  useIsomorphicLayoutEffect(() => {
    updateMask(true)
  }, [])

  return withNativeProps(
    props,
    <div
      className={classPrefix}
      style={{
        direction,
      }}
    >
      <div
        className={`${classPrefix}-header`}
      >
        <animated.div
          className={classNames(
            `${classPrefix}-header-mask`,
            `${classPrefix}-header-mask-left`,
          )}
          style={{
            opacity: leftMaskOpacity,
          }}
        />
        <animated.div
          className={classNames(
            `${classPrefix}-header-mask`,
            `${classPrefix}-header-mask-right`,
          )}
          style={{
            opacity: rightMaskOpacity,
          }}
        />
        <animated.div
          className={`${classPrefix}-tab-list`}
          ref={tabListContainerRef}
          scrollLeft={scrollLeft}
          onScroll={updateMask}
          role="tablist"
        >
          <animated.div
            ref={activeLineRef}
            className={`${classPrefix}-tab-line`}
            style={{
              width:
                activeLineMode === 'fixed'
                  ? 'var(--fixed-active-line-width, 30px)'
                  : width,
              x,
            }}
          />
          {panes.map((pane) => withNativeProps(
            pane.props,
            <div
              key={pane.key}
              className={classNames(`${classPrefix}-tab-wrapper`, {
                [`${classPrefix}-tab-wrapper-stretch`]: stretch,
              })}
            >
              <div
                className={classNames(`${classPrefix}-tab`, {
                  [`${classPrefix}-tab-active`]: pane.key === actActiveKey,
                  [`${classPrefix}-tab-disabled`]: pane.props.disabled,
                })}
                onClick={() => {
                  const { key } = pane
                  if (pane.props.disabled) return
                  if (key === undefined || key === null) return
                  setActActiveKey(key.toString())
                }}
                role="tab"
                tabIndex={pane.props.disabled ? 0 : 1}
                aria-selected={pane.key === actActiveKey}
              >
                {pane.props.title}
              </div>
            </div>,
          ))}
        </animated.div>
      </div>
      {panes.map((pane) => {
        if (pane.props.children === undefined) {
          return null
        }
        const active = pane.key === actActiveKey
        return (
          <ShouldRender
            key={pane.key}
            active={active}
            forceRender={pane.props.forceRender}
            destroyOnClose={pane.props.destroyOnClose}
          >
            <div
              className={`${classPrefix}-content`}
              style={{ display: active ? 'block' : 'none' }}
            >
              {pane.props.children}
            </div>
          </ShouldRender>
        )
      })}
    </div>,
  )
}
